﻿using System;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Xml.Serialization;
using HttpServer.Exceptions;
using HttpServer.Helpers;
using HttpServer.MVC.Rendering.Spark;
using HttpServer.Sessions;
using Spark;

namespace HttpServer.MVC.Controllers
{
    /// <summary>
    /// The abstract SparkController controller integrates Spark templates
    /// by adding Render methods. Use this controller as the base for other
    /// controllers that use the Spark View Engine.
    /// </summary>
    public abstract class SparkController : RequestController
    {
        private readonly ISparkServiceContainer _Container;
        private NameValueCollection _Errors = new NameValueCollection();

        /// <summary>
        /// Creates a new <see cref="SparkController"/>.
        /// </summary>
        protected SparkController(ISparkServiceContainer container)
        {
            _Container = container;
        }

        /// <summary>
        /// Creates a new <see cref="SparkController"/>.
        /// </summary>
        /// <param name="controller">Prototype to copy information from.</param>
        protected SparkController(SparkController controller)
            : base(controller)
        {
            _Container = controller._Container;
        }

        protected ISparkServiceContainer Container { get { return _Container;} }

        /// <summary>
        /// A set of errors that occurred during request processing.
        /// Key should be argument name (if argument error, otherwise <see cref="String.Empty"/>), value should be
        /// the error message.
        /// </summary>
        /// <remarks>Errors can be rendered into templates using the <see cref="WebHelper.Errors"/> method.</remarks>
        /// <seealso cref="WebHelper"/>
        [XmlElement("Errors")]
        protected NameValueCollection Errors
        {
            get { return _Errors; }
            set { _Errors = value; }
        }

        /// <summary>
        /// Render template for the currently invoked method.
        /// </summary>
        /// <param name="args">arguments/parameters used in template</param>
        /// <returns>template generated content</returns>
        /// <remarks>calls RenderActionWithErrors</remarks>
        protected string Render(SparkDataDictionary args)
        {
            return RenderAction(MethodName, args);
        }

        /// <summary>
        /// Render contents into a template.
        /// </summary>
        /// <param name="method">method/template to generate</param>
        /// <param name="args">arguments/parameters used in template</param>
        /// <returns>template generated content</returns>
        /// <remarks>calls RenderActionWithErrors.</remarks>
        protected virtual string RenderAction(string method, SparkDataDictionary args)
        {
            return RenderTemplate(ControllerName, method, args);
        }

        /// <summary>
        /// Renders errors from the <see cref="Errors"/> property into the
        /// current method template, or as a JavaScript alert if the request is Ajax.
        /// </summary>
        /// <param name="method">name of the currently invoked method.</param>
        /// <param name="arguments">arguments used in the method template.</param>
        /// <returns>generated string</returns>
        /// <remarks>Creates a JavaScript Alert box if request is Ajax.</remarks>
        protected string RenderErrors(string method, SparkDataDictionary arguments)
        {
            if (_Errors.Count > 0)
            {
                if (Request.IsAjax)
                    return RenderJsErrors(_Errors);
            }

            return RenderAction(method, arguments);
        }

        /// <summary>
        /// Switches content-type to "text/JavaScript" and returns content.
        /// </summary>
        /// <param name="js">JavaScript to send to the client.</param>
        /// <returns>JavaScript</returns>
        protected string RenderJavascript(string js)
        {
            Response.ContentType = "text/javascript";
            return js;
        }

        /// <summary>
        /// Creates a JavaScript "alert" filled with all errors.
        /// </summary>
        /// <param name="errors"></param>
        /// <returns>a</returns>
        protected string RenderJsErrors(NameValueCollection errors)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("alert('");
            for (int i = 0; i < errors.Count; ++i)
                sb.Append(errors[i].Replace("'", "\\'") + "\\n");
            sb.Append("');");

            Response.ContentType = "text/javascript";
            return sb.ToString();
        }

        /// <summary>
        /// Render a template.
        /// </summary>
        /// <remarks>Merges the Arguments property with the <c>args</c> parameter and pass those to the template.</remarks>
        /// <param name="controller">controller name are used as a folder name when looking for the template.</param>
        /// <param name="method">method are used as filename when looking for the template.</param>
        /// <param name="args">arguments that should be passed to the template.</param>
        /// <returns></returns>
        protected string RenderTemplate(string controller, string method, SparkDataDictionary args)
        {
            try
            {
                string template = Path.Combine(controller, method + ".spark");

                ISparkViewEngine engine = _Container.GetService<ISparkViewEngine>();


                // Compile and instantiate the template.
                SparkView view = (SparkView)engine.CreateInstance(
                                                new SparkViewDescriptor()
                                                    .AddTemplate(template));

                // Pass the model data and context to the view.
                view.ViewData = args;
                view.Request = Request;
                view.Response = Response;
                view.Session = Session;

                // Write it to a string and return.
                using (var writer = new StringWriter())
                {
                    view.RenderView(writer);
                    writer.Flush();

                    // Clear the error buffer.
                    _Errors.Clear();

                    return writer.ToString();
                }
            }
            catch (FileNotFoundException err)
            {
                throw new NotFoundException("Failed to find template. Details: " + err.Message, err);
            }
            catch (InvalidOperationException err)
            {
                throw new InternalServerException("Failed to render template. Details: " + err.Message, err);
            }
            catch (ArgumentException err)
            {
#if DEBUG
                throw new InternalServerException(
                    "Failed to render template, reason: " + err.ToString().Replace("\r\n", "<br />\r\n"), err);
#else
                throw new InternalServerException("Failed to render templates", err);
#endif
            }
        }

        /// <summary>
        /// Invoked each time a new request is about to be invoked.
        /// </summary>
        /// <remarks>Can be used to clear old data.</remarks>
        /// <param name="request"></param>
        /// <param name="response"></param>
        /// <param name="session"></param>
        protected override void SetupRequest(IHttpRequest request, IHttpResponse response, IHttpSession session)
        {
            _Errors.Clear();
            base.SetupRequest(request, response, session);
        }
    }
}
